import os,sys
from enum import Enum
from collections import defaultdict
import jinja2

#头文件的扩展名
INCLUDE_EXT=('.h','.hpp')
#源代码的扩展名
SOURCE_EXT=('.c','.C','.cpp','.CPP','.cxx','.CXX')
CMAKELIST_TEMPLATE='''
#cmake最低版本要求
cmake_minimum_required (VERSION 2.8)
#引入共通内容
#include(../../common.cmake)

########################
#常用预定义变量
#PROJECT_SOURCE_DIR：工程的根目录
#PROJECT_BINARY_DIR：运行 cmake 命令的目录，通常是 ${PROJECT_SOURCE_DIR}/build
#PROJECT_NAME：返回通过 project 命令定义的项目名称
#CMAKE_CURRENT_SOURCE_DIR：当前处理的 CMakeLists.txt 所在的路径
#CMAKE_CURRENT_BINARY_DIR：target 编译目录
#CMAKE_CURRENT_LIST_DIR：CMakeLists.txt 的完整路径
#CMAKE_CURRENT_LIST_LINE：当前所在的行
#CMAKE_MODULE_PATH：定义自己的 cmake 模块所在的路径，SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)，然后可以用INCLUDE命令来调用自己的模块
#EXECUTABLE_OUTPUT_PATH：重新定义目标二进制可执行文件的存放位置
#LIBRARY_OUTPUT_PATH：重新定义目标链接库文件的存放位置
#CMAKE_MAJOR_VERSION：cmake 主版本号，比如 3.4.1 中的 3
#CMAKE_MINOR_VERSION：cmake 次版本号，比如 3.4.1 中的 4
#CMAKE_PATCH_VERSION：cmake 补丁等级，比如 3.4.1 中的 1
#CMAKE_SYSTEM：系统名称，比如 Linux-­2.6.22
#CMAKE_SYSTEM_NAME：不包含版本的系统名，比如 Linux
#CMAKE_SYSTEM_VERSION：系统版本，比如 2.6.22
#CMAKE_SYSTEM_PROCESSOR：处理器名称，比如 i686
#UNIX：在所有的类 UNIX 平台下该值为 TRUE，包括 OS X 和 cygwin
#WIN32：在所有的 win32 平台下该值为 TRUE，包括 cygwin
########################

#项目名
project({{info.project_name}})
#编译模式
set(CMAKE_BUILD_TYPE "Debug")
#执行shell命令，获取当前路径
execute_process(
    COMMAND pwd
    OUTPUT_VARIABLE PWD
)
#cmake生成时打印信息
message("build with ${CMAKE_BUILD_TYPE} mode,working direction is ${PWD}")
#DEBUG模式下的编译选项
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -g")
#RELEASE下的编译选项
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3")
#设置可以使用相对路径
set(CMAKE_USE_RELATIVE_PATHS "ON")
#设置详细输出
set(CMAKE_VERBOSE_MAKEFILE "ON")
#设置编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11 -Wall")
{% if info.find_all_src %}
#列出所有源文件
set(SOURCES
{% for item in info.sources %}${CMAKE_CURRENT_SOURCE_DIR}{{info.path_sep}}{{item}}
{% endfor %}){% else %}
#搜索所有源码，保存至变量；非递归搜索，文件有增删时需要重新生成makefile
#例子:
#aux_source_directory(. SOURCES){% for item in info.source_paths%}
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}{{info.path_sep}}{{item}} SOURCES){% endfor %}
#也可以这么写
#file(GLOB SOURCES "*.cpp" "src/*.cpp"){% endif %}
#添加依赖的库路径
#link_directories(path)
{% if info.project_type == 'execute'  %}
#编译结果是可执行程序，输出到bin
add_executable({{info.project_name}} ${SOURCES})
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}{{info.path_sep}}bin)
{% elif  info.project_type == 'static' %}
#编译结果是静态库，输出到lib
add_library({{info.project_name}} STATIC ${SOURCES})
set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}{{info.path_sep}}lib)
{% elif  info.project_type == 'shared' %}
#编译结果是动态库，输出到lib
add_library({{info.project_name}} SHARED ${SOURCES})
set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}{{info.path_sep}}lib)
{% endif %}
#头文件所在路径
include_directories({% for item in info.include_paths %}
${CMAKE_CURRENT_SOURCE_DIR}{{info.path_sep}}{{item}}{% endfor %}
)
#添加依赖的库名，库有依赖关系时被依赖的写在后面
#target_link_libraries( {{info.project_name}}
#    PRIVATE pthread
#)
# 如果有子项目的话请在此添加文件夹名
#add_subdirectory(subproject)
'''

class dir_type(Enum):
    NONE_DIR=0
    INCLUDE_DIR=1
    SOURCE_DIR=2
    #既有头文件又有源代码
    ALL_DIR=3

class folders_info:
    def __init__(self):
        self.dir_path=''
        self.type=dir_type.NONE_DIR
        self.files=[]
    def __str__(self):
        return 'dir:{}|type:{}|files:{}'.format(self.dir_path,self.type.name,self.files)

class project_info:
    def __init__(self):
        self.project_name=''
        self.sources=[]
        self.project_type=''
        self.include_paths=[]
        self.find_all_src = True
        self.path_sep = os.path.sep
    def proc_info(self):
        '根据设置的参数进行统计，需要在所有属性设置完成后调用'
        source_paths=[os.path.dirname(x) for x in self.sources]
        self.source_paths = list(set(source_paths))
        #print(self.source_paths)

#递归搜索指定路径，返回存有指定文件格式的路径和文件名
def scan_path(spath):
    result=defaultdict(folders_info)
    for x in os.walk(spath):
        #避免中间结果影响输出
        if 'build' in x[0].split(os.path.sep):
            continue
        for file_name in x[2]:
            file_ext = os.path.splitext(file_name)[1]
            if file_ext in SOURCE_EXT:
                obj=result[x[0]]
                obj.dir_path = os.path.sep.join(x[0].split(os.path.sep)[1:])
                obj.files.append(file_name)
                if obj.type == dir_type.INCLUDE_DIR:
                    obj.type=dir_type.ALL_DIR
                elif obj.type == dir_type.NONE_DIR:
                    obj.type=dir_type.SOURCE_DIR
            else:
                #已经记录了，这种情况是目录中有源码也有头文件，目前在处理头文件
                if x[0] in result.keys():
                    continue
                if file_ext in INCLUDE_EXT:
                    obj=result[x[0]]
                    obj.dir_path = os.path.sep.join(x[0].split(os.path.sep)[1:])
                    #头文件的话只用记一个路径就行，不需要记文件
                    #obj.files.append(file_name)
                    obj.type=dir_type.INCLUDE_DIR
    return result.values()#仅返回列表就行了，不需要key

def gen_cmake_list(lfolders_info,proj):
    '''
        根据传入的路径属性、配置信息，生成CMakeLists.txt
    '''
    source_files=[]
    include_path=[]
    for x in lfolders_info:
        if x.type == dir_type.SOURCE_DIR or x.type == dir_type.ALL_DIR:         
            source_files.extend([os.path.join(x.dir_path,f) for f in x.files])
        if x.type == dir_type.INCLUDE_DIR or x.type == dir_type.ALL_DIR:
            include_path.append(x.dir_path)

    proj.sources = source_files
    proj.include_paths = include_path
    proj.proc_info()
    template = jinja2.Template(CMAKELIST_TEMPLATE)
    #print(template.render(info=proj))
    #print(os.path.dirname(source_files[0]))
    #print(source_files)
    #print(include_path)
    with open('CMakeLists.txt','w+',encoding='utf8') as f:
        f.write(template.render(info=proj))

if __name__ == "__main__":
    dirs = scan_path('.')
    proj = project_info()
    proj.project_name = 'PROJECT'
    proj.project_type='static'
    proj.find_all_src = False
    gen_cmake_list(dirs,proj)